home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Magnum One
/
Magnum One (Mid-American Digital) (Disc Manufacturing).iso
/
d18
/
gsdb21.arc
/
GS_DBNDX.PAS
< prev
next >
Wrap
Pascal/Delphi Source File
|
1991-01-04
|
57KB
|
1,320 lines
{
dBase III Index Handler
GS_DBNdx Copyright (c) Richard F. Griffin
15 November 1990
102 Molded Stone Pl
Warner Robins, GA 31088
-------------------------------------------------------------
This unit handles the objects for all dBase III index (.NDX)
operations.
changes:
16 Nov 90 - Modified KeyUpdate sub-procedure KeyInsert to
test for end-of-file during search for key.
}
{.pa}
{
┌─────────────────────┐
│ INTERFACE SECTION │
└─────────────────────┘
}
unit GS_DBNdx;
(*$N+,E+*) {Numeric coprocessor or emulation is}
{required to handle the double type}
{that dBase uses to store number and}
{date fields. If not using date or }
{numeric values, 16K of memory can}
{be avoided by deleting this and}
{changing double types to integer}
interface
uses
GS_Strng, {String handler routines}
GS_Error, {Error handler routines}
GS_FileH; {File handler routines}
const
NdxBufSize = 16384;
type
{
┌──────────────────────────────────────────────────────────┐
│ ******** Index Header Description ******** │
│ │
│ This record type describes the index file header. │
│ This is a 512-byte block that is located at the │
│ beginning of the index file. Refer to Appendix C │
│ for a description of the fields. │
└──────────────────────────────────────────────────────────┘
}
GS_Indx_Head = Record
Root : Longint;
Next_Blk : Longint;
Unknwn1 : Longint;
Key_Lgth : Integer;
Max_Keys : Integer;
Data_Typ : Integer;
Entry_Sz : Integer;
Unknwn2 : Longint;
Key_Form : array [0..487] of char;
end;
{
┌──────────────────────────────────────────────────────────┐
│ ******** Index Node Header Description ******** │
│ │
│ This record type describes the index file node header. │
│ Each node is a 512-byte block that is used as nodes │
│ to store keys and pointers. Refer to Appendix C │
│ for a description of the fields. │
└──────────────────────────────────────────────────────────┘
}
GS_Indx_Data = Record
Entry_Ct : Integer;
Unknwn1 : Integer;
Data_Ary : array [0..507] of byte;
{Memory array holding key entries}
Filler1 : array [0..255] of byte;
{Filler for possible overflow during}
{insert mode.}
end;
GS_Indx_EntPtr = ^GS_Indx_Etry; {Pointer of type GS_Indx_Etry. Will}
{be used to reference key entries }
{from GS_Indx_Data.Data_Ary.}
{
┌──────────────────────────────────────────────────────────┐
│ ******** Index Node Key Entry Description ******* │
│ │
│ This record type describes the index file key entries. │
│ Refer to Appendix C for a description of each field. │
└──────────────────────────────────────────────────────────┘
}
GS_Indx_Etry = Record
Block_Ax : Longint;
Recrd_Ax : Longint;
case Integer of
0 : (Char_Fld : array [1..255] of char);
1 : (Numb_Fld : double);
{dBase numeric and date fields are}
{stored as a floating point double}
end;
{
┌────────────────────────────────────────────────────────┐
│ Work table used to step through nodes. The previous │
│ nodes must be saved for finding the next or previous │
│ record during sequential reads. │
└────────────────────────────────────────────────────────┘
}
GS_Indx_Tabl = Record
Page_No : Longint; {Disk block holding node info}
Etry_No : Longint; {Last entry used in node}
Last_One : Longint; {Number of keys in this node }
Node_Pag : Boolean; {True for non-leaf nodes}
end;
GS_Indx_LPtr = ^GS_dBase_IX; {Pointer to object. Used by GS_dBase_DB}
{
┌─────────────────────────────────┐
│ GS_dBase_IX Object Definition │
└─────────────────────────────────┘
}
GS_dBase_IX = object
Ndx_Name : String[64]; {File name of index file}
Ndx_Hdr : GS_Indx_Head; {Index header information}
Ndx_File : file; {File type for index file}
Ndx_Tabl : array [0..25] of GS_Indx_Tabl;
{Array of 25 table entries to hold}
{the trail of non-leaf nodes that are}
{traversed during a key search. This }
{table is needed to track positions for}
{sequential reads (next and previous).}
Ndx_Lvl : integer; {Holds counter into Ndx_Tabl}
Ndx_Data : GS_Indx_Data; {Node header information}
Ndx_Pntr : GS_Indx_EntPtr; {Pointer to key entry information}
Ndx_Key_St : string[127]; {Holds last key value found on call to}
{either KeyRead or KeyFind}
Ndx_Key_Num : longint; {Holds last physical record number for a}
{key value found on call to either}
{KeyRead or KeyFind}
Ndx_Key_Form : string[127]; {Holds the key formula in type string}
KeyEOF : boolean; {True if last KeyRead attempted to read}
{beyond the range of index keys - either}
{beyond beginning or end of file}
ExactMatch : boolean; {Flag for type of test to use in KeyFind}
{It will force a match against an entire}
{key if true, and only for the length of}
{the passed argument if false. It is}
{initialized true.}
{
┌───────────────────────────────────────────────────────────────────────┐
│ *** These methods are described individually in the following *** │
│ pages. Their name describes their function. │
└───────────────────────────────────────────────────────────────────────┘
}
FUNCTION Init(IName : String) : boolean;
FUNCTION KeyFind(st : String) : longint;
FUNCTION KeyLocRec(rec : longint) : boolean;
FUNCTION KeyRead(a : LongInt) : longint;
PROCEDURE KeyUpdate (st : string; rec, crec : longint);
PROCEDURE Ndx_Close;
PROCEDURE Ndx_Get(blk : longint);
PROCEDURE Ndx_GetRecEntry;
PROCEDURE Ndx_GetRecPage(Ascnd : boolean);
FUNCTION Ndx_LastEntry : boolean;
PROCEDURE Ndx_Make(filname, formla : string; lth : integer; typ : char);
PROCEDURE Ndx_NodeData(pn, en, lo : longint; np : boolean);
PROCEDURE Ndx_Put(blk : longint);
Procedure KeyList(st : string);
end;
{.pa}
{
┌──────────────────────────┐
│ IMPLEMENTATION SECTION │
└──────────────────────────┘
}
implementation
const
Next_Record = -1; {Token value passed to read next record}
Prev_Record = -2; {Token value passed to read previous record}
Top_Record = -3; {Token value passed to read first record}
Bttm_Record = -4; {Token value passed to read final record}
ValueHigh = 1; {Token value passed for key comparison high}
ValueLow = -1; {Token value passed for key comparison low}
ValueEqual = 0; {Token value passed for key comparison equal}
var
Work_Key : string; {Holds key passed in Find and KeyUpdate}
Work_Lth : integer; {Holds Length of Work_Key }
Work_Num : Double; {Holds numeric value of Work_Key if needed}
RPag : Longint; {Work variable to hold current index block}
RNum : Longint; {Work variable for record number}
IsAscend : Boolean; {Flag for ascending/descending status.}
{Set based on Next/Previous Record read}
{.pa}
{
Ndx_Make
╔══════════════════════════════════════════════════════════════════╗
║ ║
║ The Ndx_Make method will create an index file ║
║ ║
║ Calling the Method: ║
║ ║
║ objectname.Ndx_Make(filname, formla, lth, typ) ║
║ ║
║ ( where objectname is of type GS_dBase_IX ║
║ filename is of type string ║
║ formla is of type string) ║
║ lth is of type integer for key length ║
║ typ is of type char for field type ║
║ ║
║ Result: ║
║ ║
║ The index file is created. ║
║ ║
╚══════════════════════════════════════════════════════════════════╝
}
Procedure GS_dBase_IX.Ndx_Make(filname, formla : string; lth : integer;
typ : char);
begin
Ndx_Name := filname+'.NDX'; {Setup file name}
GS_FileAssign(Ndx_File,Ndx_Name,NdxBufSize);
GS_FileRewrite(Ndx_File,1);
FillChar(Ndx_Hdr, SizeOf(Ndx_Hdr),#0);
Ndx_Hdr.Root := 1;
Ndx_Hdr.Next_Blk := 2;
case typ of
'N',
'D' : begin
Ndx_Hdr.Data_Typ := 1;
lth := 8;
end;
else Ndx_Hdr.Data_Typ := 0;
end;
Ndx_Hdr.Key_Lgth := lth;
Ndx_Hdr.Max_Keys := (SizeOf(Ndx_Hdr)-4) div (lth+8);
Ndx_Hdr.Entry_Sz := lth+8;
CnvStrToAsc(formla,Ndx_Hdr.Key_Form, length(formla)+1);
move(Ndx_Hdr, Ndx_Data, SizeOf(Ndx_Hdr));
Ndx_Put(0);
FillChar(Ndx_Data, SizeOf(Ndx_Data),#0);
Ndx_Put(1);
end;
{.pa}
{
INIT
╔══════════════════════════════════════════════════════════════════╗
║ ║
║ The INIT method initializes objectname by reading the .NDX ║
║ file and loading file structure information into the object. ║
║ ║
║ Calling the Method: ║
║ ║
║ oldindex := objectname.Init(String) ║
║ ║
║ ( where oldindex is of type boolean, ║
║ objectname is of type GS_dBase_IX, ║
║ String is the file name of the dBase ║
║ file (without the .NDX extension). ║
║ ║
║ Result: ║
║ ║
║ Index file object is initialized. ║
║ True will be returned if file exists. ║
║ ║
╚══════════════════════════════════════════════════════════════════╝
┌──────────────────────────────────────────┐
│ The INIT method will do the following: │
│ 1. Open the index file │
│ 2. Read the first block (header) │
│ into objectname. │
│ 3. Set Ndx_Lvl to zero, which will │
│ indicate no reads performed. │
│ 4. Return flag (false if new file) │
└──────────────────────────────────────────┘
}
function GS_dBase_IX.Init(IName : String) : boolean;
var
i : integer;
begin
Ndx_Name := IName + '.NDX';
if GS_FileExists(Ndx_File, Ndx_Name) then
begin
GS_FileAssign(Ndx_File,Ndx_Name,NdxBufSize);
GS_FileReset(Ndx_File,1);
Init := true;
end
else
begin
ShowError(2,Ndx_Name);
Init := false; {return a flag showing no file}
end;
Ndx_Get(0); {Read first block of file for header info}
{Note that no error checking is done }
{in this version }
move(Ndx_Data, Ndx_Hdr, 512); {Store in header info area}
Ndx_Lvl := 0; {Initialize the node step table}
Ndx_Tabl[0].Page_No := 0;
Ndx_Tabl[0].Etry_No := 0;
Ndx_Tabl[0].Last_One := 0;
KeyEOF := false; {Initialize EOF Flag to false}
ExactMatch := true; {Initialize to use an exact match test}
{
┌──────────────────────────────────────────┐
│ This portion of code will extract the │
│ "formula", which is usually the field │
│ that is used for indexing. However, it │
│ can be compound (FLDA+FLDB). The │
│ formula is placed in a string for use │
│ during index updates. │
└──────────────────────────────────────────┘
}
move(Ndx_Hdr.Key_Form[0], Ndx_Key_Form[1],100);
i := 1;
while Ndx_Key_Form[i] <> #0 do inc(i);
Ndx_Key_Form[0] := chr(pred(i));
Ndx_Key_Form := TrimR(Ndx_Key_Form);
Ndx_Key_Form := TrimL(Ndx_Key_Form);
end;
{.pa}
{
KEYFIND
╔══════════════════════════════════════════════════════════════════╗
║ ║
║ The KeyFind method will return the physical record location ║
║ of the record matching the key value passed as the argument. ║
║ ExactMatch controls the length of the match check. If ║
║ ExactMatch is true, the entire key in the .NDX entry must ║
║ match the value passed. If false, the check will only be ║
║ for the length of the string passed. ║
║ ║
║ Calling the Method: ║
║ ║
║ longintvalu := objectname.KeyFind(string) ║
║ ║
║ ( where objectname is of type GS_dBase_IX, ║
║ string is a value used to search the ║
║ .NDX file looking for a match. ║
║ ║
║ Result: ║
║ ║
║ 1. longintvalu will point to the physical record, ║
║ or will be zero if no match. ║
║ 2. Ndx_Key_St will contain the key value. ║
║ 3. Ndx_Key_Num will contain the record number. ║
║ ║
╚══════════════════════════════════════════════════════════════════╝
}
function GS_dBase_IX.KeyFind(st : string) : LongInt;
var
i : integer; {Work variable}
rl : integer; {Result code for Val procedure}
ct : integer; {Variable to hold BlockRead byte count}
Less_Than : boolean; {Flag to hunt for key match}
Loop_Cnt : longint;
Match_Cnd : integer;
{
┌─────────────────────────────────────┐
│ This routine sets up the match │
│ string. It sets the length of the │
│ match for full or partial, and │
│ converts to numeric if needed. │
└─────────────────────────────────────┘
}
procedure SetMatchValue;
begin
FillChar(Work_Key[1], SizeOf(Work_Key), ' '); {Fill with blanks}
Work_Key := st;
if ExactMatch then
Work_Key[0] := chr(Ndx_Hdr.Key_Lgth);
Work_Lth := length(Work_Key);
if Ndx_Hdr.Data_Typ <> 0 then
begin
val(st,Work_Num,rl);
if rl <> 0 then ShowError(501,st);
move(Work_Num, Work_Key[1], 8);
Work_Lth := 8;
Work_Key[0] := chr(Work_Lth);
end;
end;
procedure StoreMatchValue;
begin
move(Ndx_Pntr^.Char_Fld,Ndx_Key_St[1],Work_Lth);
{Move the key field to Ndx_Key_St.}
Ndx_Key_St[0] := chr(Work_Lth); {Now insert the length into Ndx_Key_St}
end;
function DoMatchValue : integer;
var
nks : double;
begin
if Ndx_Hdr.Data_Typ = 0 then {Character key field}
if Ndx_Key_St > Work_Key then Match_Cnd := ValueHigh
else if Ndx_Key_St = Work_Key then Match_Cnd := ValueEqual
else Match_Cnd := ValueLow
else {Numeric key field}
begin
move(Ndx_Key_St[1],nks,8);
if nks > Work_Num then Match_Cnd := ValueHigh
else if nks = Work_Num then Match_Cnd := ValueEqual
else Match_Cnd := ValueLow;
end;
DoMatchValue := Match_Cnd;
end;
begin
KeyEOF := false; {Reset End-of-File to false}
Ndx_Key_Num := 0; {Initialize}
Ndx_Key_St := ''; {Initialize}
Ndx_Lvl := 0; {Initialize index level}
SetMatchValue; {Set key comparison value}
RPag := Ndx_Hdr.Root; {Get root node address}
while RPag <> 0 do {While a non-leaf node, do this}
begin
Ndx_Get(RPag); {Get Node using RPag as block number}
Ndx_Pntr := Addr(Ndx_Data.Data_Ary[0]);
{Get pointer to first entry}
Loop_Cnt := Ndx_Pntr^.Block_Ax; {Get the next node pointer to see if it}
{is zero, meaning a leaf node}
i := 0; {Initialize i as counter}
Less_Than := Ndx_Data.Entry_Ct > 0;
{Start out with less than flag true}
{Will be false if Entry Count is 0}
{which means an empty node}
while (less_than) and (i <= Ndx_Data.Entry_Ct) do
{Hunt for a match. If i = last entry in}
{the node, the last entry is used for}
{the next node search}
begin
Ndx_Pntr := Addr(Ndx_Data.Data_Ary[i * Ndx_Hdr.Entry_Sz]);
{Get pointer to entry indexed by i}
inc(i); {Increment the counter}
StoreMatchValue; {Put the key value in Ndx_Key_St for}
{matching}
Less_Than := DoMatchValue = ValueLow;
{Test looking for greater or equal than}
{the key value. Less_Than will be set}
{false when found, setting the condition}
{to leave this portion of the routine}
end;
{
┌──────────────────────────────────────────┐
│ Save the node data for this node as: │
│ 1. Block Number from RPag. │
│ 2. Entry number of match or last one. │
│ 3. Set total number of entries. This │
│ is entry count+1 for non-leaf nodes │
│ 4. Set non-leaf flag to true. │
└──────────────────────────────────────────┘
}
Ndx_NodeData(RPag,i,Ndx_Data.Entry_Ct+1,true);
if Loop_Cnt = 0 then RPag := 0
else RPag := Ndx_Pntr^.Block_Ax;
{Get the next node in the tree}
end;
Ndx_Tabl[Ndx_Lvl].Node_Pag := false;
{Set non-leaf flag to false for this}
{last level}
dec(Ndx_Tabl[Ndx_Lvl].Last_One);
{Set total number of entries to the }
{correct value for a leaf node}
if Ndx_Data.Entry_Ct = 0 then
begin
KeyFind := 0;
exit;
end;
if (DoMatchValue <> ValueEqual) or
(Ndx_Tabl[Ndx_Lvl].Last_One < Ndx_Tabl[Ndx_Lvl].Etry_No)
then Ndx_Key_Num := 0 {if unable to find a match, the above}
{routine would have stopped when a}
{greater key was found, or would have}
{continued to Last_One. Since the entry}
{count is one less for leaf nodes, even}
{if there was a match at Last_one, it is}
{not valid, and was only a coincidence.}
{In either case, set record number = 0.}
else
Ndx_Key_Num := Ndx_Pntr^.Recrd_Ax;
{When there is a match with the key,}
{get the physical record number}
KeyFind := Ndx_Key_Num; {Return with the record number}
end;
{.pa}
{
KEYLOCREC
╔══════════════════════════════════════════════════════════════════╗
║ ║
║ The KeyLocRec method will search the .NDX file to find the ║
║ matching index entry pointing to the physical record location ║
║ of the record requested. ║
║ ║
║ Calling the Method: ║
║ ║
║ flag := objectname.KeyLocRec(key, position) ║
║ ║
║ ( where objectname is of type GS_dBase_IX, ║
║ key is the key string ║
║ position is the physical record number ║
║ of the matching .DBF record.) ║
║ ║
║ Result: ║
║ ║
║ Boolean True is returned if a match is found. ║
║ The current index entry will be set to the record ║
║ if a match does exist. ║
║ ║
╚══════════════════════════════════════════════════════════════════╝
}
Function GS_dBase_IX.KeyLocRec (rec : longint) : boolean;
var
lr : longint;
begin
if rec = Ndx_Key_Num then
begin {Exit if already at the record}
KeyLocRec := true;
exit;
end;
lr := KeyRead(Top_Record);
while (not KeyEOF) and (lr <> rec) do lr := KeyRead(Next_Record);
if (KeyEOF) then KeyLocRec := false
else KeyLocRec := true;
end;
{.pa}
{
KEYREAD
╔══════════════════════════════════════════════════════════════════╗
║ ║
║ The KeyRead method will return the physical record location ║
║ of the record requested. The only options that may be asked ║
║ for are Top, Bottom, Next, and Previous. ║
║ ║
║ Calling the Method: ║
║ ║
║ longintvalu := objectname.KeyRead(position) ║
║ ║
║ ( where objectname is of type GS_dBase_IX, ║
║ position is in -1 to -4, ║
║ longintvalu is physical record number ║
║ of the matching .DBF record. ║
║ ║
║ Result: ║
║ ║
║ longintvalu will point to the physical record. ║
║ ║
╚══════════════════════════════════════════════════════════════════╝
}
FUNCTION GS_dBase_IX.KeyRead(a : longint) : longint;
var
N_L_Hold : Integer; {Tempory variable for index level}
ct : Integer; {Work variable for Blockread count}
{
┌───────────────────────────────────────────────┐
│ Start of KeyRead function. This will │
│ accomplish the following: │
│ │
│ 1. If first time for index, set any call │
│ for a Next or Previous read to a Top │
│ read command. │
│ 2. Use case select for Top/Bttm/Next/Prev. │
│ Return physical .DBF record in RNum. │
│ 3. If not a valid action, set RNum to 0. │
│ 4. Move key value to Ndx_Key_St. │
│ 5. Move RNum to Ndx_Key_Num. │
│ 6. Return RNum value to calling procedure. │
└───────────────────────────────────────────────┘
}
{ Start of KeyRead }
begin
RNum := a; {Get action command}
if ((a = Next_Record) or (a = Prev_Record)) and
(Ndx_Lvl = 0) then RNum := Top_Record;
{if first time through, use Top_Record}
{command instead}
KeyEOF := false; {End-of-File initially set false}
case RNum of {Select KeyRead Action}
Next_Record : begin
IsAscend := true;
{Will be an ascending read}
N_L_Hold := Ndx_Lvl;
{Save old index level}
{
┌─────────────────────────────────────┐
│ If the last record read was the │
│ last entry in the node, you have │
│ to step back through the index │
│ levels to find the next node. │
└─────────────────────────────────────┘
}
if Ndx_LastEntry then
{If last entry in node already used,}
{go find the next node}
begin
while (Ndx_LastEntry) and (Ndx_Lvl > 0) do
dec(Ndx_Lvl);
{Step back through the levels until you}
{find a good one, or run out of levels.}
if Ndx_Lvl = 0 then
{if out of levels, process for EOF}
begin
Ndx_Lvl := N_L_Hold;
{Get old level number to restore}
KeyEOF := true;
{Set End-of-File true}
end else
begin {Otherwise, get next entry data}
inc(Ndx_Tabl[Ndx_Lvl].Etry_No);
{Step to next Entry Number}
Ndx_GetRecEntry;
{Go search for next good record}
end;
end
else inc(Ndx_Tabl[Ndx_Lvl].Etry_No);
{Otherwise, just step to next entry}
Ndx_Pntr :=
Addr(Ndx_Data.Data_Ary[(
(Ndx_Tabl[Ndx_Lvl].Etry_No - 1) *
Ndx_Hdr.Entry_Sz)]);
{Get pointer to the key entry}
RNum := Ndx_Pntr^.Recrd_Ax;
{Get record number for the key entry}
end;
Prev_Record : begin
IsAscend := false;
{Will be a descending read}
N_L_Hold := Ndx_Lvl;
{Save old index level}
{
┌─────────────────────────────────────┐
│ If the last record read was the │
│ first entry in the node, you have │
│ to step back through the index │
│ levels to find the next node. │
└─────────────────────────────────────┘
}
if Ndx_Tabl[Ndx_Lvl].Etry_No = 1 then
{If last entry in node already used,}
{go find the next node}
begin
while (Ndx_Tabl[Ndx_Lvl].Etry_No = 1) and
(Ndx_Lvl > 0) do
dec(Ndx_Lvl);
{Step back through the levels until you}
{find a good one, or run out of levels.}
if Ndx_Lvl = 0 then
{if out of levels, process for EOF}
begin
Ndx_Lvl := N_L_Hold;
{Get old level number to restore}
KeyEOF := true;
{Set End-of-File true}
end else
begin {Otherwise, get next entry data}
dec(Ndx_Tabl[Ndx_Lvl].Etry_No);
{Step to next Entry Number}
Ndx_GetRecEntry;
{Go search for next good record}
end;
end
else dec(Ndx_Tabl[Ndx_Lvl].Etry_No);
{Otherwise, just step to next entry}
Ndx_Pntr :=
Addr(Ndx_Data.Data_Ary[(
(Ndx_Tabl[Ndx_Lvl].Etry_No - 1) *
Ndx_Hdr.Entry_Sz)]);
{Get pointer to the key entry}
RNum := Ndx_Pntr^.Recrd_Ax;
{Get record number for the key entry}
end;
Top_Record,
Bttm_Record : begin
IsAscend := Top_Record = RNum;
{Ascending search if Top, otherwise}
{descending. An ascending search will}
{return the first index key as the Top.}
{A descending search will return the}
{last index key as the 'Top'}
Ndx_Lvl := 0; {Clear index levels for new stack}
RPag := Ndx_Hdr.Root;
{Get root node address}
Ndx_GetRecPage(IsAscend);
{Go get valid record}
end;
else RNum := 0; {If no valid action, return zero}
end;
move(Ndx_Pntr^.Char_Fld,Ndx_Key_St[1],Ndx_Hdr.Key_Lgth);
{Move the key field to Ndx_Key_St.}
{The Move procedure must be used since}
{Char_Fld is not a true Pascal string.}
Ndx_Key_St[0] := chr(Ndx_Hdr.Key_Lgth);
{Now insert the length into Ndx_Key_St}
{so it is a valid string we can use}
Ndx_Key_Num := RNum; {Save RNum in Ndx_Key_Num}
KeyRead := RNum; {Return RNum}
end;
{.pa}
{
NDX_CLOSE
╔══════════════════════════════════════════════════════════════════╗
║ ║
║ The Ndx_Close method will close the index file from this ║
║ object. ║
║ ║
║ Calling the Method: ║
║ ║
║ objectname.Ndx_Close ║
║ ║
║ ( where objectname is of type GS_dBase_IX ║
║ ║
║ Result: ║
║ ║
║ The index file is closed. ║
║ ║
╚══════════════════════════════════════════════════════════════════╝
}
Procedure GS_dBase_IX.Ndx_Close;
begin
GS_FileClose(Ndx_File);
end;
{.pa}
{
NDX_GET
╔══════════════════════════════════════════════════════════════════╗
║ ║
║ The Ndx_Get method will read a block from the index file for ║
║ this object. ║
║ ║
║ Calling the Method: ║
║ ║
║ objectname.Ndx_Get(Blk) ║
║ ║
║ ( where objectname is of type GS_dBase_IX ║
║ blk is longint number of block to read) ║
║ ║
║ Result: ║
║ ║
║ The index block (node) is read into Ndx_Data ║
║ ║
╚══════════════════════════════════════════════════════════════════╝
}
Procedure GS_dBase_IX.Ndx_Get(blk : longint);
var
r : word;
begin
GS_FileRead(Ndx_File,blk*512,Ndx_Data,512,r);
if r < 512 then ShowError(100,'Ndx_Get');
end;
Procedure GS_dBase_IX.Ndx_NodeData(pn, en, lo : longint; np : boolean);
begin
inc(Ndx_Lvl); {Prepare to store node information as}
{part of the Ndx_Lvl hierarchy}
with Ndx_Tabl[Ndx_Lvl] do {Use the index level entry}
begin
Page_No := pn; {Save Block number}
Etry_No := en; {Set entry number}
Last_One := lo; {Set total number of entries.}
Node_Pag := np; {Set non-leaf flag}
end;
end;
{
┌─────────────────────────────────────┐
│ This procedure will locate the │
│ starting page to search for an │
│ entry. It selects the entry │
│ number contained at the present │
│ index level and passes its node │
│ pointer to Get_PageRec. This is │
│ needed to read the index blocks in │
│ the correct sequence. │
└─────────────────────────────────────┘
}
procedure GS_dBase_IX.Ndx_GetRecEntry;
begin
RPag := Ndx_Tabl[Ndx_Lvl].Page_No;
{Get page number for this index level}
Ndx_Get(RPag); {Get Node using RPag as block number}
Ndx_Pntr := Addr(Ndx_Data.Data_Ary[(Ndx_Tabl[Ndx_Lvl].Etry_No- 1)
* Ndx_Hdr.Entry_Sz]);
{Get pointer to key entry (relative zero)}
RPag := Ndx_Pntr^.Block_Ax; {Get Next node number in RPag}
Ndx_GetRecPage(IsAscend); {Go get the next record from a non-leaf}
{node. Pass the argument for either an}
{ascending or descending search}
end;
{
┌─────────────────────────────────────┐
│ This procedure will step the nodes │
│ until it finds a leaf node. The │
│ starting node is contained in the │
│ variable RPag; the record number │
│ of the first or last key (based) │
│ on Ascnd) will be placed in RNum. │
└─────────────────────────────────────┘
}
procedure GS_dBase_IX.Ndx_GetRecPage(Ascnd : boolean);
var
ec : integer; {Work variable for entry count}
begin
while RPag <> 0 do {Next node number in RPag will be zero}
{when taken from a leaf node.}
begin
Ndx_Get(RPag); {Get Node using RPag as block number}
Ndx_NodeData(RPag,0,Ndx_Data.Entry_Ct+1,true);
{Store Node data}
{
┌───────────────────────────────────────────────┐
│ This portion of code checks to see if called │
│ by Next/Top or Bttm/Prev, and sets the entry │
│ to 1 or last node entry, based on Ascnd │
└───────────────────────────────────────────────┘
}
if Ascnd then
begin
ec := 0; {Set ec = first entry (relative zero)}
Ndx_Tabl[Ndx_Lvl].Etry_No := 1;
{Set Entry Number in table to first one}
end else
begin
ec := Ndx_Data.Entry_Ct; {Set ec to last entry (relative zero)}
{Note there are Entry_Ct+1 entries for}
{non-leaf nodes. It will be adjusted}
{later if it is a leaf node}
Ndx_Tabl[Ndx_Lvl].Etry_No := ec+1;
{Set Entry Number in table to last one}
end;
Ndx_Pntr := Addr(Ndx_Data.Data_Ary[ec * Ndx_Hdr.Entry_Sz]);
{Get pointer to correct entry in node}
RPag := Ndx_Pntr^.Block_Ax; {Get Next node number in RPag}
end;
{
┌───────────────────────────────────────────────┐
│ This portion of code checks to see if the │
│ index file is empty. If so, the EOF is set │
│ and the routine is quit. │
└───────────────────────────────────────────────┘
}
if Ndx_Data.Entry_Ct = 0 then
begin
KeyEOF := true;
RNum := 0;
exit;
end;
Ndx_Tabl[Ndx_Lvl].Node_Pag := false;
{Set non-leaf flag to false for leaf}
if not Ascnd then
begin
dec(Ndx_Tabl[Ndx_Lvl].Etry_No);
{Set Entry Number in table to last one}
{for a non-leaf node}
Ndx_Pntr := Addr(Ndx_Data.Data_Ary[ec-1 * Ndx_Hdr.Entry_Sz]);
{Get pointer to correct leaf entry for}
{the last entry in the node}
end;
Ndx_Tabl[Ndx_Lvl].Node_Pag := false;
{Set non-leaf flag to false for this}
{last level}
dec(Ndx_Tabl[Ndx_Lvl].Last_One); {Set total number of entries to the }
{correct value for a leaf node}
RNum := Ndx_Pntr^.Recrd_Ax; {Get the physical record number for}
{the first key entry}
end;
{
┌───────────────────────────────────────────────┐
│ This function will return true if all │
│ entries have been processed in the │
│ Ndx_Lvl layer number passed to the function │
└───────────────────────────────────────────────┘
}
function GS_dBase_IX.Ndx_LastEntry : boolean;
begin
if Ndx_Tabl[Ndx_Lvl].Etry_No = Ndx_Tabl[Ndx_Lvl].Last_One then
Ndx_LastEntry := true else Ndx_LastEntry := false;
end;
{.pa}
{
NDX_PUT
╔══════════════════════════════════════════════════════════════════╗
║ ║
║ The Ndx_Put method will write a block from the index file for ║
║ this object. ║
║ ║
║ Calling the Method: ║
║ ║
║ objectname.Ndx_Put(Blk) ║
║ ║
║ ( where objectname is of type GS_dBase_IX ║
║ blk is longint number of block to write) ║
║ ║
║ Result: ║
║ ║
║ The index block (node) is written from Ndx_Data ║
║ ║
╚══════════════════════════════════════════════════════════════════╝
}
Procedure GS_dBase_IX.Ndx_Put(blk : longint);
var
r : word;
begin
GS_FileWrite(Ndx_File,blk*512,Ndx_Data,512,r);
if r < 512 then ShowError(101,'Ndx_Put');
end;
Procedure GS_dBase_IX.KeyUpdate (st : string; rec, crec : longint);
var
ct : integer;
nu_key : longint;
em_hold : boolean; {holds ExactMatch flag during this}
t_num : double;
lr,
b1,
b2 : longint;
rlst,
e1,
e2,
n1,
n2 : integer;
s1,
s2 : string[127];
r1 : GS_Indx_Data;
Procedure FixKeyLength;
begin
FillChar(Work_Key[1], 255, ' '); {Fill with blanks}
Work_Key := st;
if Ndx_Hdr.Data_Typ = 0 then
begin
Work_Key[0] := chr(Ndx_Hdr.Key_Lgth);
Work_Lth := Ndx_Hdr.Key_Lgth;
end
else
begin
val(st,Work_Num,rlst);
if rlst <> 0 then ShowError(501,st);
move(Work_Num, Work_Key[1], 8);
Work_Lth := 8;
Work_Key[0] := #8;
end;
end;
Procedure DeleteEntry;
begin
with Ndx_Tabl[Ndx_Lvl] do
begin
move(Ndx_Data.Data_Ary[(Etry_No)*Ndx_Hdr.Entry_Sz],
Ndx_Data.Data_Ary[(Etry_No-1)*Ndx_Hdr.Entry_Sz],
Ndx_Hdr.Entry_Sz*(Last_One-Etry_No));
dec(Last_One);
dec(Ndx_Data.Entry_Ct);
end;
end;
Procedure InsertEntry;
begin
with Ndx_Tabl[Ndx_Lvl] do
begin
if (Etry_No <> 0) and (not KeyEOF) then
begin
move(Ndx_Data.Data_Ary[(Etry_No-1)*Ndx_Hdr.Entry_Sz],
Ndx_Data.Data_Ary[(Etry_No)*Ndx_Hdr.Entry_Sz],
Ndx_Hdr.Entry_Sz*(((Last_One-Etry_No)+1)));
Ndx_Pntr := Addr(Ndx_Data.Data_Ary[(Etry_No-1) * Ndx_Hdr.Entry_Sz]);
end
else
begin
Ndx_Pntr := Addr(Ndx_Data.Data_Ary[Etry_No*Ndx_Hdr.Entry_Sz]);
inc(Etry_No);
end;
inc(Last_One);
inc(Ndx_Data.Entry_Ct);
move(Work_Key[1],Ndx_Pntr^.Char_Fld,Work_Lth)
{Move the key field from Work_Key.}
{The Move procedure must be used since}
{Char_Fld is not a true Pascal string.}
end;
end;
procedure ReplacePointerEntry;
begin
while (Ndx_LastEntry) and (Ndx_Lvl > 0) do dec(Ndx_Lvl);
{Search for entry that requires the key}
{value. Value is not needed for the}
{last entry in a non-leaf node.}
if Ndx_Lvl > 0 then
begin {Replace key value with new one}
Ndx_Get(Ndx_Tabl[Ndx_Lvl].Page_No);
Ndx_Pntr := Addr(Ndx_Data.Data_Ary
[(Ndx_Tabl[Ndx_Lvl].Etry_No-1) * Ndx_Hdr.Entry_Sz]);
move(Ndx_Key_St[1],Ndx_Pntr^.Char_Fld,Work_Lth);
{Move the key field from Ndx_Key_St.}
Ndx_Put(Ndx_Tabl[Ndx_Lvl].Page_No);
{Write updated node to disk}
end;
end;
Procedure KeyDelete;
begin
DeleteEntry;
Ndx_Put(Ndx_Tabl[Ndx_Lvl].Page_No);
if Ndx_Tabl[Ndx_Lvl].Last_One = 0 then
begin
dec(Ndx_Lvl);
if Ndx_Lvl > 0 then
begin
Ndx_Get(Ndx_Tabl[Ndx_Lvl].Page_No);
KeyDelete;
end;
exit;
end;
if Ndx_Tabl[Ndx_Lvl].Etry_No > Ndx_Tabl[Ndx_Lvl].Last_One then
begin
Ndx_Pntr := Addr(Ndx_Data.Data_Ary
[(Ndx_Tabl[Ndx_Lvl].Last_One-1) * Ndx_Hdr.Entry_Sz]);
move(Ndx_Pntr^.Char_Fld,Ndx_Key_St[1],Work_Lth);
{Move the key field to Ndx_Key_St.}
{The Move procedure must be used since}
{Char_Fld is not a true Pascal string.}
Ndx_Key_St[0] := chr(Work_Lth);
{Now insert the length into Ndx_Key_St}
{so it is a valid string we can use}
dec(Ndx_Lvl);
if Ndx_Lvl > 0 then ReplacePointerEntry;
end;
end;
Procedure SplitBlock;
begin
b1 := Ndx_Hdr.Next_Blk;
inc(Ndx_Hdr.Next_Blk);
Ndx_NodeData(b1,1,Ndx_Tabl[Ndx_Lvl].Last_One,Ndx_Tabl[Ndx_Lvl].Node_Pag);
with Ndx_Tabl[Ndx_Lvl] do
begin
n1 := Ndx_Lvl;
Ndx_Data.Entry_Ct := Last_One div 2;
e2 := Last_One - Ndx_Data.Entry_Ct;
Last_One := Ndx_Data.Entry_Ct;
e1 := Last_One;
if Node_Pag then dec(Ndx_Data.Entry_Ct);
Ndx_Pntr := Addr(Ndx_Data.Data_Ary
[(Ndx_Tabl[Ndx_Lvl].Last_One-1) * Ndx_Hdr.Entry_Sz]);
move(Ndx_Pntr^.Char_Fld,s1[1],Work_Lth);
s1[0] := chr(Work_Lth);
Ndx_Put(Page_No);
end;
dec(Ndx_Lvl);
with Ndx_Tabl[Ndx_Lvl] do
begin
b2 := Page_No;
n2 := Ndx_Lvl;
Last_One := e2;
Ndx_Data.Entry_Ct := e2;
if Node_Pag then dec(Ndx_Data.Entry_Ct);
move(Ndx_Data.Data_Ary[e1*Ndx_Hdr.Entry_Sz],
Ndx_Data.Data_Ary[0],Ndx_Hdr.Entry_Sz*(e2));
Ndx_Put(Page_No);
move(Ndx_Hdr, Ndx_Data, 512);
{Store from header info area}
Ndx_Put(0);
dec(Ndx_Lvl);
end;
end;
Procedure MakeRootNode;
begin
Ndx_Lvl := 0;
with Ndx_Tabl[Ndx_Lvl] do
begin
Page_No := Ndx_Hdr.Next_Blk;
inc(Ndx_Hdr.Next_Blk);
Ndx_Hdr.Root := Page_No;
move(Ndx_Hdr, Ndx_Data, 512);
{Store from header info area}
Ndx_Put(0);
Ndx_Pntr := Addr(Ndx_Data.Data_Ary[0]);
Ndx_Data.Entry_Ct := 0;
Ndx_Pntr^.Recrd_Ax := 0;
Ndx_Pntr^.Block_Ax := b2;
Last_One := 1;
Etry_No := 1;
Ndx_Put(Page_No);
end;
end;
procedure ExpandIndex;
var
kEOF : boolean;
begin
SplitBlock;
if Ndx_Lvl = 0 then MakeRootNode;
Work_Key := s1;
Ndx_Get(Ndx_Tabl[Ndx_Lvl].Page_No);
kEOF := KeyEOF;
KeyEOF := false;
InsertEntry;
KeyEOF := kEOF;
Ndx_Pntr^.Recrd_Ax := 0;
Ndx_Pntr^.Block_Ax := b1;
if Ndx_Tabl[Ndx_Lvl].Last_One <= Ndx_Hdr.Max_Keys then
begin
Ndx_Put(Ndx_Tabl[Ndx_Lvl].Page_No);
end else
begin
ExpandIndex;
end;
end;
Procedure KeyInsert;
begin
nu_key := KeyFind(st);
if nu_key <> 0 then
begin
if Ndx_Hdr.Data_Typ = 0 then
while (Ndx_Key_St = Work_Key) and (not KeyEOF) do
nu_key := KeyRead(Next_Record)
else
begin
move(Ndx_Key_St[1],t_num,8);
while (t_num = Work_Num) and (not KeyEOF) do
nu_key := KeyRead(Next_Record);
end;
end;
InsertEntry;
Ndx_Pntr^.Recrd_Ax := rec;
Ndx_Pntr^.Block_Ax := 0;
if Ndx_Tabl[Ndx_Lvl].Etry_No > Ndx_Tabl[Ndx_Lvl].Last_One then
begin
r1 := Ndx_Data;
n1 := Ndx_Lvl;
Ndx_Key_St := Work_Key;
ReplacePointerEntry;
Ndx_Lvl := n1;
Ndx_Data := r1;
end;
if Ndx_Tabl[Ndx_Lvl].Last_One <= Ndx_Hdr.Max_Keys then
begin
Ndx_Put(Ndx_Tabl[Ndx_Lvl].Page_No);
end else
begin
ExpandIndex;
end;
end;
begin
FixKeyLength;
if rec = crec then {Tests for Append vs Update}
begin
if Work_Key = Ndx_Key_St then exit;
KeyDelete;
end;
em_hold := ExactMatch;
ExactMatch := true;
KeyInsert;
ExactMatch := em_hold;
if crec < 0 then exit;
lr := KeyFind(st);
while lr <> rec do lr := KeyRead(Next_Record);
end;
Procedure GS_dBase_IX.KeyList(st : string);
var
ofil : text;
RPag : LongInt;
Lst_One,
i,j,k,v : integer;
rl : integer;
ct : integer;
recnode,
Less_Than : boolean;
begin
assign(ofil, st);
ReWrite(ofil);
with Ndx_Hdr do
begin
writeln(ofil,'--------------------------------------------------');
writeln(ofil,'':8,Ndx_Key_St);
writeln(ofil,'Root =',Root:3,' Next Block Available:',Next_Blk:3);
end;
RPag := 1;
while RPag <> Ndx_Hdr.Next_Blk do
begin
Seek(Ndx_File,RPag*512);
BlockRead(Ndx_File,Ndx_Data,512,ct);
Lst_One := Ndx_Data.Entry_Ct+1;
write(ofil,RPag:2,' [',Ndx_Data.Entry_Ct,'] ');
Ndx_Pntr := Addr(Ndx_Data.Data_Ary[0]);
recnode := Ndx_Pntr^.Block_Ax = 0;
k := Lst_One;
if recnode then dec(k);
v := 1;
i := 1;
while (i <= k) do
begin
Ndx_Pntr := Addr(Ndx_Data.Data_Ary[((i-1) * Ndx_Hdr.Entry_Sz)]);
with Ndx_Pntr^ do
begin
write(ofil,'':v,Block_Ax:3);
v := 9;
if i = Lst_One then write(ofil,' - empty')
else
begin
write(ofil,Recrd_Ax:4,' ');
write(ofil,Numb_Fld:6:0);
{ for j := 1 to 5 do write(ofil,Char_Fld[j]);}
end;
WRITELN(OFIL);
end;
inc(i);
end;
writeln(ofil);
inc(RPag);
end;
System.Close(ofil);
end;
end.